package Translate;

import java.util.List;
import Translate.Temp.*;

public abstract class Tree {
    public static boolean isAtom(Tree t) {
        return t instanceof Exp.CONST || t instanceof Exp.NAME
            || t instanceof Exp.TEMP;
    }

    public abstract Exp[] kids();

    public abstract <D, R> R accept(Visitor<D, R> v, D d);

    public static abstract class Exp extends Tree {
        public abstract Exp clone(Exp[] kids);

        public static class BINOP extends Exp {
            public enum Operator {
                ADD("ADD"),
                AND("AND"),
                DIV("DIV"),
                DIVU("DIVU"),
                MUL("MUL"),
                OR("OR"),
                SLL("SLL"),
                SRA("SRA"),
                SRL("SRL"),
                SUB("SUB"),
                XOR("XOR");

                final String string;

                Operator(String s) {
                    string = s;
                }

                public String toString() {
                    return string;
                }
            }

            public final Operator op;
            public final Exp left, right;

            public BINOP(Operator o, Exp l, Exp r) {
                op = o;
                left = l;
                right = r;
            }

            public Exp[] kids() {
                return new Exp[] { left, right };
            }

            public Exp clone(Exp[] kids) {
                return new BINOP(op, kids[0], kids[1]);
            }

            public <D, R> R accept(Visitor<D, R> v, D d) {
                return v.visit(this, d);
            }
        }

        public static class CALL extends Exp {
            public final Exp func;
            public final Exp[] args;

            public CALL(Exp f, Exp[] a) {
                func = f;
                args = a;
            }

            public Exp[] kids() {
                Exp[] kids = new Exp[args.length + 1];
                kids[0] = func;
                for (int i = 0; i < args.length; i++)
                    kids[i + 1] = args[i];
                return kids;
            }

            public Exp clone(Exp[] kids) {
                Exp[] args = new Exp[kids.length - 1];
                for (int i = 0; i < args.length; i++)
                    args[i] = kids[i + 1];
                return new CALL(kids[0], args);
            }

            public <D, R> R accept(Visitor<D, R> v, D d) {
                return v.visit(this, d);
            }
        }

        public static class CONST extends Exp {
            public final int value;

            public CONST(int v) {
                value = v;
            }

            public Exp[] kids() {
                return new Exp[] {};
            }

            public Exp clone(Exp[] kids) {
                return this;
            }

            public <D, R> R accept(Visitor<D, R> v, D d) {
                return v.visit(this, d);
            }
        }

        public static class ESEQ extends Exp {
            public final Stm stm;
            public final Exp exp;

            public ESEQ(Stm s, Exp e) {
                stm = s;
                exp = e;
            }

            public Exp[] kids() {
                throw new Error("kids() not applicable to ESEQ");
            }

            public Exp clone(Exp[] kids) {
                throw new Error("clone() not applicable to ESEQ");
            }

            public <D, R> R accept(Visitor<D, R> v, D d) {
                return v.visit(this, d);
            }
        }

        public static class MEM extends Exp {
            public final Exp exp;
            public final CONST offset;

            public MEM(Exp e, CONST c) {
                exp = e;
                offset = c;
            }

            public Exp[] kids() {
                return new Exp[] { exp };
            }

            public Exp clone(Exp[] kids) {
                return new MEM(kids[0], offset);
            }

            public <D, R> R accept(Visitor<D, R> v, D d) {
                return v.visit(this, d);
            }
        }

        public static class NAME extends Exp {
            public final Label label;

            public NAME(Label l) {
                label = l;
            }

            public Exp[] kids() {
                return new Exp[] {};
            }

            public Exp clone(Exp[] kids) {
                return this;
            }

            public <D, R> R accept(Visitor<D, R> v, D d) {
                return v.visit(this, d);
            }
        }

        public static class TEMP extends Exp {
            public final Temp temp;

            public TEMP(Temp t) {
                temp = t;
            }

            public Exp[] kids() {
                return new Exp[] {};
            }

            public Exp clone(Exp[] kids) {
                return this;
            }

            public <D, R> R accept(Visitor<D, R> v, D d) {
                return v.visit(this, d);
            }
        }
    }

    public static abstract class Stm extends Tree {
        public abstract Stm clone(Exp[] kids);

        public static class CJUMP extends Stm {
            public enum Operator {
                BEQ("BEQ"),
                BNE("BNE"),
                BGE("BGE"),
                BLE("BLE"),
                BGT("BGT"),
                BLT("BLT");
                private String string;

                Operator(String s) {
                    string = s;
                }

                public String toString() {
                    return string;
                }
            }

            public final Operator op;
            public final Exp left, right;
            public final Label iftrue, iffalse;

            public CJUMP(Operator o, Exp l, Exp r, Label t, Label f) {
                op = o;
                left = l;
                right = r;
                iftrue = t;
                iffalse = f;
            }

            public Exp[] kids() {
                return new Exp[] { left, right };
            }

            public Stm clone(Exp[] kids) {
                return new CJUMP(op, kids[0], kids[1], iftrue, iffalse);
            }

            public CJUMP not() {
                switch (op) {
                case BEQ:
                    return new CJUMP(Operator.BNE, left, right, iffalse, iftrue);
                case BNE:
                    return new CJUMP(Operator.BEQ, left, right, iffalse, iftrue);
                case BGE:
                    return new CJUMP(Operator.BLT, left, right, iffalse, iftrue);
                case BLE:
                    return new CJUMP(Operator.BGT, left, right, iffalse, iftrue);
                case BGT:
                    return new CJUMP(Operator.BLE, left, right, iffalse, iftrue);
                case BLT:
                    return new CJUMP(Operator.BGE, left, right, iffalse, iftrue);
                default:
                    throw new Error();
                }
            }

            public CJUMP swap() {
                switch (op) {
                case BEQ:
                    return new CJUMP(Operator.BEQ, right, left, iftrue, iffalse);
                case BNE:
                    return new CJUMP(Operator.BEQ, right, left, iftrue, iffalse);
                case BGE:
                    return new CJUMP(Operator.BLE, right, left, iftrue, iffalse);
                case BLE:
                    return new CJUMP(Operator.BGE, right, left, iftrue, iffalse);
                case BGT:
                    return new CJUMP(Operator.BLT, right, left, iftrue, iffalse);
                case BLT:
                    return new CJUMP(Operator.BGT, right, left, iftrue, iffalse);
                default:
                    throw new Error();
                }
            }

            public <D, R> R accept(Visitor<D, R> v, D d) {
                return v.visit(this, d);
            }
        }

        public static class ESTM extends Stm {
            public final Exp exp;

            public ESTM(Exp e) {
                exp = e;
            }

            public Exp[] kids() {
                return new Exp[] { exp };
            }

            public Stm clone(Exp[] kids) {
                return new ESTM(kids[0]);
            }

            public <D, R> R accept(Visitor<D, R> v, D d) {
                return v.visit(this, d);
            }
        }

        public static class JUMP extends Stm {
            public final Exp exp;
            public final Label[] targets;

            public JUMP(Exp e, Label[] t) {
                exp = e;
                targets = t;
            }

            public JUMP(Label target) {
                exp = new Exp.NAME(target);
                targets = new Label[] { target };
            }

            public Exp[] kids() {
                return new Exp[] { exp };
            }

            public Stm clone(Exp[] kids) {
                return new JUMP(kids[0], targets);
            }

            public <D, R> R accept(Visitor<D, R> v, D d) {
                return v.visit(this, d);
            }
        }

        public static class LABEL extends Stm {
            public final Label label;

            public LABEL(Label l) {
                label = l;
            }

            public Exp[] kids() {
                return new Exp[] {};
            }

            public Stm clone(Exp[] kids) {
                return this;
            }

            public <D, R> R accept(Visitor<D, R> v, D d) {
                return v.visit(this, d);
            }
        }

        public static class MOVE extends Stm {
            public final Exp dst, src;

            public MOVE(Exp d, Exp s) {
                dst = d;
                src = s;
            }

            public Exp[] kids() {
                if (dst instanceof Exp.MEM)
                    return new Exp[] { ((Exp.MEM) dst).exp, src };
                return new Exp[] { src };
            }

            public Stm clone(Exp[] kids) {
                if (dst instanceof Exp.MEM) {
                    Exp.MEM m = (Exp.MEM) dst;
                    return new MOVE(new Exp.MEM(kids[0], m.offset), kids[1]);
                }
                return new MOVE(dst, kids[0]);
            }

            public <D, R> R accept(Visitor<D, R> v, D d) {
                return v.visit(this, d);
            }
        }

        public static class SEQ extends Stm {
            public final Stm left, right;

            public SEQ(Stm l, Stm r) {
                left = l;
                right = r;
            }

            public Exp[] kids() {
                throw new Error("kids() not applicable to SEQ");
            }

            public Stm clone(Exp[] kids) {
                throw new Error("clone() not applicable to SEQ");
            }

            public <D, R> R accept(Visitor<D, R> v, D d) {
                return v.visit(this, d);
            }
        }
    }

    public static interface Visitor<Domain, Range> {
        public abstract Range visit(Stm.SEQ n, Domain d);

        public abstract Range visit(Stm.LABEL n, Domain d);

        public abstract Range visit(Stm.JUMP n, Domain d);

        public abstract Range visit(Stm.MOVE n, Domain d);

        public abstract Range visit(Stm.ESTM n, Domain d);

        public abstract Range visit(Stm.CJUMP n, Domain d);

        public abstract Range visit(Exp.MEM n, Domain d);

        public abstract Range visit(Exp.TEMP n, Domain d);

        public abstract Range visit(Exp.ESEQ n, Domain d);

        public abstract Range visit(Exp.NAME n, Domain d);

        public abstract Range visit(Exp.CONST n, Domain d);

        public abstract Range visit(Exp.CALL n, Domain d);

        public abstract Range visit(Exp.BINOP n, Domain d);
    }

    public static class Print implements Visitor<Integer, Void> {
        java.io.PrintWriter out;

        public Print(java.io.PrintWriter o, Stm s, int i) {
            out = o;
            s.accept(this, i);
        }

        public Print(java.io.PrintWriter o, Stm s) {
            this(o, s, 0);
            o.println();
        }

        public Print(java.io.PrintWriter o, List<Stm> stms) {
            out = o;
            for (Stm s : stms) {
                s.accept(this, 0);
                o.println();
            }
            o.flush();
        }

        private void indent(int d) {
            for (int i = 0; i < d; i++)
                out.print(' ');
        }

        private void sayln(String s) {
            out.println(s);
            out.flush();
        }

        private void say(String s) {
            out.print(s);
        }

        private void say(Object o) {
            out.print(o.toString());
        }

        public Void visit(Stm.SEQ s, Integer d) {
            indent(d);
            sayln("SEQ(");
            s.left.accept(this, d + 1);
            sayln(",");
            s.right.accept(this, d + 1);
            say(")");
            return null;
        }

        public Void visit(Stm.LABEL s, Integer d) {
            indent(d);
            say("LABEL ");
            say(s.label);
            return null;
        }

        public Void visit(Stm.JUMP s, Integer d) {
            indent(d);
            sayln("JUMP(");
            s.exp.accept(this, d + 1);
            say(")");
            return null;
        }

        public Void visit(Stm.CJUMP s, Integer d) {
            indent(d);
            say(s.op.toString());
            sayln("(");
            s.left.accept(this, d + 1);
            sayln(",");
            s.right.accept(this, d + 1);
            sayln(",");
            indent(d + 1);
            say(s.iftrue);
            say(", ");
            say(s.iffalse);
            say(")");
            return null;
        }

        public Void visit(Stm.MOVE s, Integer d) {
            indent(d);
            sayln("MOVE(");
            s.dst.accept(this, d + 1);
            sayln(",");
            s.src.accept(this, d + 1);
            say(")");
            return null;
        }

        public Void visit(Stm.ESTM s, Integer d) {
            indent(d);
            sayln("ESTM(");
            s.exp.accept(this, d + 1);
            say(")");
            return null;
        }

        public Void visit(Exp.BINOP e, Integer d) {
            indent(d);
            say(e.op.toString());
            sayln("(");
            e.left.accept(this, d + 1);
            sayln(",");
            e.right.accept(this, d + 1);
            say(")");
            return null;
        }

        public Void visit(Exp.MEM e, Integer d) {
            indent(d);
            sayln("MEM(");
            e.exp.accept(this, d + 1);
            say(", ");
            say(e.offset.value);
            say(")");
            return null;
        }

        public Void visit(Exp.TEMP e, Integer d) {
            indent(d);
            say("TEMP ");
            say(e.temp);
            return null;
        }

        public Void visit(Exp.ESEQ e, Integer d) {
            indent(d);
            sayln("ESEQ(");
            e.stm.accept(this, d + 1);
            sayln(",");
            e.exp.accept(this, d + 1);
            say(")");
            return null;
        }

        public Void visit(Exp.NAME e, Integer d) {
            indent(d);
            say("NAME ");
            say(e.label);
            return null;
        }

        public Void visit(Exp.CONST e, Integer d) {
            indent(d);
            say("CONST ");
            say(String.valueOf(e.value));
            return null;
        }

        public Void visit(Exp.CALL e, Integer d) {
            indent(d);
            sayln("CALL(");
            e.func.accept(this, d + 1);
            for (int i = 0; i < e.args.length; i++) {
                sayln(",");
                e.args[i].accept(this, d + 1);
            }
            say(")");
            return null;
        }
    }
}
